const uuidv4 = require("uuid/v4");
const Hashids = require("hashids");
const URL = require("url").URL;
const hashids = new Hashids();
const { genJwtToken } = require("./jwt_helper");

const re = /(\S+)\s+(\S+)/;

// Note: express http converts all headers
// to lower case.
const AUTH_HEADER = "authorization";
const BEARER_AUTH_SCHEME = "bearer";

function parseAuthHeader(hdrValue) {
  if (typeof hdrValue !== "string") {
    return null;
  }
  const matches = hdrValue.match(re);
  return matches && { scheme: matches[1], value: matches[2] };
}

const fromAuthHeaderWithScheme = function(authScheme) {
  const authSchemeLower = authScheme.toLowerCase();
  return function(request) {
    let token = null;
    if (request.headers[AUTH_HEADER]) {
      const authParams = parseAuthHeader(request.headers[AUTH_HEADER]);
      if (authParams && authSchemeLower === authParams.scheme.toLowerCase()) {
        token = authParams.value;
      }
    }
    return token;
  };
};

const fromAuthHeaderAsBearerToken = function() {
  return fromAuthHeaderWithScheme(BEARER_AUTH_SCHEME);
};

const appTokenFromRequest = fromAuthHeaderAsBearerToken();

// app token to validate the request is coming from the authenticated server only.
// 业务{名字：令牌}，业务名字 必须和 originAppName对应
const appTokenDB = {
    sso_ankuranand: "l1Q7zkOL59cRqWBkQ12ZiGVW2DBL",
  simple_sso_consumer: "1g0jJwGmRQhJwvwNOrY4i90kD0m"
};
const alloweOrigin = {
    "http://sso.ankuranand.com:3020": true,
    "http://consumer.ankuranand.in:3020": true,
    "http://consumertwo.ankuranand.in:3030": true,
    "http://sso.ankuranand.in:3080": false
};


const deHyphenatedUUID = () => uuidv4().replace(/-/gi, "");
const encodedId = () => hashids.encodeHex(deHyphenatedUUID());

// A temporary cahce to store all the application that has login using the current session.
// It can be useful for variuos audit purpose
const sessionUser = {};
const sessionApp = {};

//业务列表    网址：业务name 名字
const originAppName = {
  "http://sso.ankuranand.com:3020": "sso_ankuranand",
  "http://consumer.ankuranand.in:3020": "sso_consumer",
  "http://consumertwo.ankuranand.in:3030": "simple_sso_consumer"
};

const userDB = {
  "info@ankuranand.com": {
    password: "test",
    userId:"info@ankuranand.com",
    //userId: encodedId(), // incase you dont want to share the user-email.
      //这里是配置他有哪些应用的权限，同样是appname
    appPolicy: {
        sso_ankuranand: { role: "admin", shareEmail: true },
      simple_sso_consumer: { role: "user", shareEmail: false }
    }
  }
};

// these token are for the validation purpose
const intrmTokenCache = {};//Token缓存

//填补intrmTokenCache
const fillIntrmTokenCache = (origin, id, intrmToken) => {
  intrmTokenCache[intrmToken] = [id, originAppName[origin]];
  //因为是随机id，所以只要不重启服务，intrmTokenCache会一直增加数据，不知道这里的用意？
    //intrmTokenCache[随机id] =  [用户id，网站业务name]
};
//存储本地数据。（业务网址，用户id，随机id）
const storeApplicationInCache = (origin, id, intrmToken) => {
    console.log("sessionApp：")
    console.log("用户id:"+id)
    console.log(sessionApp)
    //重启服务后sessionApp 为空，连续登陆 会记录 之前的信息

    //null 就是重启服务后 还没登陆过， 这里就是以前登陆过
  if (sessionApp[id] == null) {
    sessionApp[id] = {
      //originAppName[origin] 就是网站业务name
      [originAppName[origin]]: true
    };
    fillIntrmTokenCache(origin, id, intrmToken);
  } else {
    //sessionApp 大概定义的就是 用户可以访问的业务应用。
    sessionApp[id][originAppName[origin]] = true;
    fillIntrmTokenCache(origin, id, intrmToken);
  }
    console.log("缓存");
    console.log({ ...sessionApp }, { ...sessionUser }, { intrmTokenCache });
};

const generatePayload = ssoToken => {
  const globalSessionToken = intrmTokenCache[ssoToken][0];
  const appName = intrmTokenCache[ssoToken][1];

  const userEmail = sessionUser[globalSessionToken];
  const user = userDB[userEmail];
  const appPolicy = user.appPolicy[appName];
    console.log(2222)
    console.log(appName)
    console.log(userEmail)
    console.log(user)
    console.log(appPolicy)
  const email = appPolicy.shareEmail === true ? userEmail : undefined;
  const payload = {
    ...{ ...appPolicy },
    ...{
      email,
      shareEmail: undefined,
      uid: user.userId,
      // global SessionID for the logout functionality.
      globalSessionID: globalSessionToken
    }
  };
  return payload;
};

//业务系统外部令牌验证方法。
const verifySsoToken = async (req, res, next) => {
  const appToken = appTokenFromRequest(req);

    const { ssoToken } = req.query;
  // if the application token is not present or ssoToken request is invalid
  // if the ssoToken is not present in the cache some is
  // smart.
    console.log(55555)
    console.log(appToken)//app 专门的一个id 令牌， 业务网站那边 也得配置，在那边的 ssoRedirect函数里配置，这个令牌是在中心（appTokenDB变量）配置，然后业务网站照着配置
    console.log(ssoToken)//外部系统返还的 token
    console.log(intrmTokenCache[ssoToken]) //这个应该是 查看 缓存里面 有没有这个令牌

    if (
    appToken == null ||
    ssoToken == null ||
    intrmTokenCache[ssoToken] == null
  ) {
    console.log("badRequest")
    return res.status(400).json({ message: "badRequest" });
  }

  // if the appToken is present and check if it's valid for the application
    //如果存在appToken 看看他是否有效
  const appName = intrmTokenCache[ssoToken][1];
    console.log(666)
    console.log(appName)
    console.log(6616)

    const globalSessionToken = intrmTokenCache[ssoToken][0];
  // If the appToken is not equal to token given during the sso app registraion or later stage than invalid
    //如果appToken 不在appTokenDB 里
    console.log(appToken !== appTokenDB[appName])
    console.log(appToken)
    console.log(appTokenDB[appName])
    console.log(sessionApp[globalSessionToken][appName] !== true)
  if (
    appToken !== appTokenDB[appName] ||
    sessionApp[globalSessionToken][appName] !== true
  ) {
    return res.status(403).json({ message: "Unauthorized" });
  }

  // checking if the token passed has been generated
  const payload = generatePayload(ssoToken);
  //生成令牌
    console.log("生成令牌")
    console.log(payload)
  const token = await genJwtToken(payload);
  // delete the itremCache key for no futher use,
  delete intrmTokenCache[ssoToken];
  return res.status(200).json({ token });
};
const doLogin = (req, res, next) => {
  // do the validation with email and password
  // but the goal is not to do the same in this right now,
  // like checking with Datebase and all, we are skiping these section

    //登录验证邮箱 和密码
  const { email, password } = req.body;
  if (!(userDB[email] && password === userDB[email].password)) {
    return res.status(404).json({ message: "Invalid email and password" });
  }

  // else redirect
  const { serviceURL } = req.query;
  //获取userid，这里是自动生成的随机一个id值，估计正使用是从数据库取
  //目前测试用，没有id，直接拿email地址当ip
    const id  = email

    req.session.user = id;


  sessionUser[id] = email;

  //没有serviceURL 就是是不用跳转
  if (serviceURL == null) {
    return res.redirect("/");
  }
  const url = new URL(serviceURL);
  const intrmid = encodedId();
  //存了一些东西到服务器本地
    console.log("存了一些东西到服务器本地")
    console.log(url.origin)
    console.log(id)
    console.log(intrmid)
  storeApplicationInCache(url.origin, id, intrmid);
  //给子系统的令牌 ssoToken
  return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
};


//登陆
const login = (req, res, next) => {
  //serviceURL 业务系统来源网址
  // The req.query will have the redirect url where we need to redirect after successful
  // login and with sso token.
  // This can also be used to verify the origin from where the request has came in
  // for the redirection
  const { serviceURL } = req.query;
  // direct access will give the error inside new URL.
  //过滤不允许使用登陆业务网站
  if (serviceURL != null) {
    const url = new URL(serviceURL);
    if (alloweOrigin[url.origin] !== true) {
      return res
        .status(400)
        .json({ message: "Your are not allowed to access the sso-server" });
    }
  }

  if (req.session.user != null && serviceURL == null) {
    return res.redirect("/");
  }
  // if global session already has the user directly redirect with the token
    console.log(442)
    console.log(req.session)
    console.log(req.session.user)
    console.log(serviceURL)
  if (req.session.user != null && serviceURL != null) {
    const url = new URL(serviceURL);
    const intrmid = encodedId();

    //存在缓存
      console.log(22222323)
      console.log(url.origin, req.session.user, intrmid)
    storeApplicationInCache(url.origin, req.session.user, intrmid);
    return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
  }

  return res.render("login", {
    title: "SSO-Server | Login"
  });

};

module.exports = Object.assign({}, { doLogin, login, verifySsoToken });
